package models.players;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import play.Logger;
import play.libs.Json;
import play.libs.F.Callback;
import play.libs.F.Callback0;
import play.mvc.WebSocket;
import akka.actor.ActorRef;
import models.Card;
import models.Player;
import models.exceptions.JsonOutputException;
import models.messages.AreYouHuman;
import models.messages.CloseConnection;
import models.messages.ConsoleMsg;
import models.messages.EvaluationsNames;
import models.messages.ExchangedCards;
import models.messages.ExchangingCards;
import models.messages.ItsYourTurn;
import models.messages.LicitateNow;
import models.messages.Licitating;
import models.messages.NewOpponent;
import models.messages.NewResults;
import models.messages.OpponentCards;
import models.messages.OpponentTurn;
import models.messages.OtherOpponents;
import models.messages.Quit;
import models.messages.RemoveOpponent;
import models.messages.RevealCards;
import models.messages.TellYourCards;
import models.messages.TellYourName;
import models.messages.YourCards;

public class Human extends Player
{
    public final String               name;
    public final String               uuid;
    public final ActorRef             room;
    public List<Card>                 hand;
    public boolean                    myTurn    = false;

    protected WebSocket.In<JsonNode>  in;
    protected WebSocket.Out<JsonNode> out;

    protected int                     failCount = 0;
    protected boolean                 calledToStop;

    public Human(String uuid, String name, WebSocket.In<JsonNode> in,
            WebSocket.Out<JsonNode> out, ActorRef room)
    {
        this.uuid = uuid;
        this.name = name;
        this.room = room;
        this.in = in;
        this.out = out;
        this.hand = new ArrayList<Card>();
        this.calledToStop = false;

        final ActorRef gameRoom = room;
        final String uuidCpy = uuid;
        final WebSocket.Out<JsonNode> outCpy = out;
        final boolean calledToStop = this.calledToStop;

        in.onMessage(new Callback<JsonNode>()
        {
            @Override
            public void invoke(JsonNode event)
            {
                try
                {
                    switch (event.get("type").asText())
                    {
                        case "exchangeCards":
                            if (!Human.this.myTurn)
                            {
                                throw new JsonOutputException(
                                        "It's not your tour to exchange cards.",
                                        outCpy, false);
                            }
                            List<Card> exchange = new ArrayList<Card>();
                            for (JsonNode node : event.get("cards"))
                            {
                                try
                                {
                                    exchange.add(Human.this.hand.get(node
                                            .asInt()));
                                }
                                catch (IndexOutOfBoundsException e)
                                {
                                    throw new JsonOutputException(
                                            "You can only exchange cards from your hand.",
                                            outCpy, false);
                                }
                            }
                            if (exchange.size() > 4)
                            {
                                throw new JsonOutputException(
                                        "You can exchange max 4 cards.",
                                        outCpy, false);
                            }
                            gameRoom.tell(
                                    new ExchangingCards(uuidCpy, exchange),
                                    null);
                            break;
                        case "licitation":
                            int value = event.get("value") == null ? 0 : event
                                    .get("value").asInt();
                            gameRoom.tell(
                                    new Licitating(uuidCpy, event.get(
                                            "operation").asText(), value),
                                    Human.this.getSelf());
                            break;
                        default:
                            break;
                    }
                    Human.this.failCount = 0;
                }
                catch (JsonOutputException e)
                {
                    Logger.error("[    Human    ] " + e.getMessage());
                    ObjectNode event1 = Json.newObject();
                    event1.put("type", "consoleMsg");
                    event1.put("msg", e.getMessage());
                    e.out.write(event1);
                    if (e.closeSocket)
                    {
                        e.out.close();
                    }
                }
                catch (Exception e)
                {
                    Human.this.failCount++;
                    if (Human.this.failCount > 4)
                    {

                    }
                    else
                    {

                    }
                    Logger.error("[    Human    ] " + e.getMessage());
                }
            }
        });

        in.onClose(new Callback0()
        {
            @Override
            public void invoke()
            {
                Logger.info("[    Human    ] Leaving the game (" + uuidCpy
                        + ").");
                gameRoom.tell(new Quit(uuidCpy), null);
            }
        });
    }

    @Override
    public void preStart()
    {
        Logger.debug("[    Human    ] Spawned human: " + this.uuid);
    }

    @Override
    public void postStop()
    {

        Logger.debug("[    Human    ] Killed human: " + this.uuid);
    }

    @Override
    public void onReceive(Object msg) throws Exception
    {
        try
        {
            if (msg instanceof ItsYourTurn)
            {
                ItsYourTurn yourTurn = (ItsYourTurn) msg;
                ObjectNode event = Json.newObject();
                this.myTurn = true;
                event.put("type", "changeTurn");
                event.put("player", this.name);
                ObjectNode options = event.putObject("options");
                options.put("phase", yourTurn.state.toInteger());
                options.put("check", yourTurn.canCheck);
                options.put("bet", yourTurn.canBet);
                options.put("raise", yourTurn.canRaise);
                options.put("call", yourTurn.canCall);
                options.put("fold", yourTurn.canFold);
                options.put("allin", yourTurn.canAllIn);
                options.put("maxbet", yourTurn.maxBet);
                options.put("yourmoney", yourTurn.yourMoney);
                options.put("yourbet", yourTurn.yourBet);
                options.put("pool", yourTurn.pool);
                this.out.write(event);
            }
            else if (msg instanceof AreYouHuman)
            {
                this.getSender().tell(true, this.getSelf());
            }
            else if (msg instanceof TellYourName)
            {
                this.getSender().tell(this.name, this.getSelf());
            }
            else if (msg instanceof ConsoleMsg)
            {
                ConsoleMsg consoleMsg = (ConsoleMsg) msg;
                ObjectNode event = Json.newObject();
                event.put("type", "consoleMsg");
                event.put("msgType", consoleMsg.kind);
                event.put("msg", consoleMsg.text);
                this.out.write(event);
            }
            else if (msg instanceof CloseConnection)
            {
                Logger.debug("[    Human    ] Closing connection (" + this.name
                        + ").");
                ObjectNode event = Json.newObject();
                event.put("type", "console");
                event.put("msgType", "danger");
                event.put("msg",
                        "Game room has been closed or connection has been lost.");
                this.out.write(event);
                this.out.close();
            }
            else if (msg instanceof NewOpponent)
            {
                NewOpponent newOpponent = (NewOpponent) msg;
                ObjectNode event = Json.newObject();
                event.put("type", "newPlayer");
                event.put("player", newOpponent.name);
                this.out.write(event);
            }
            else if (msg instanceof RemoveOpponent)
            {
                RemoveOpponent rmOpponent = (RemoveOpponent) msg;
                ObjectNode event = Json.newObject();
                event.put("type", "playerLeft");
                event.put("player", rmOpponent.name);
                this.out.write(event);
            }
            else if (msg instanceof OtherOpponents)
            {
                OtherOpponents otherOpponents = (OtherOpponents) msg;
                ObjectNode event;
                for (String name : otherOpponents.opponents)
                {
                    if (!this.name.equals(name))
                    {
                        event = Json.newObject();
                        event.put("type", "newPlayer");
                        event.put("player", name);
                        this.out.write(event);
                    }
                }
            }
            else if (msg instanceof YourCards)
            {
                YourCards yourCards = (YourCards) msg;
                this.hand = yourCards.cards;
                Collections.sort(this.hand, new Card.CardComparator());
                ObjectNode event = Json.newObject();
                ArrayNode jsCards = event.putArray("cards");
                event.put("type", "resetHand");
                event.put("player", this.name);
                event.put("show", true);
                for (Card c : this.hand)
                {
                    ObjectNode card = Json.newObject();
                    card.put("rank", c.rank.toString());
                    card.put("suit", c.suit.toString());
                    card.put("id", c.id);
                    jsCards.add(card);
                }
                this.out.write(event);
            }
            else if (msg instanceof ExchangedCards)
            {
                ExchangedCards exCards = (ExchangedCards) msg;
                this.hand.removeAll(exCards.oldCards);
                this.hand.addAll(exCards.newCards);
                // hand.size() == 5 ??
                Collections.sort(this.hand, new Card.CardComparator());
                ObjectNode event = Json.newObject();
                ArrayNode jsCards = event.putArray("cards");
                event.put("type", "resetHand");
                event.put("player", this.name);
                event.put("show", true);
                for (Card c : this.hand)
                {
                    ObjectNode card = Json.newObject();
                    card.put("rank", c.rank.toString());
                    card.put("suit", c.suit.toString());
                    card.put("id", c.id);
                    jsCards.add(card);
                }
                this.out.write(event);
            }
            else if (msg instanceof OpponentCards)
            {
                OpponentCards opponentCards = (OpponentCards) msg;
                ObjectNode event = Json.newObject();
                ArrayNode jsCards = event.putArray("cards");
                event.put("type", "resetHand");
                event.put("player", opponentCards.name);
                event.put("show", false);
                for (int i = 0; i < 5; ++i)
                {
                    ObjectNode card = Json.newObject();
                    card.put("rank", "two");
                    card.put("suit", "spade");
                    card.put("id", 3);
                    jsCards.add(card);
                }
                this.out.write(event);
            }
            else if (msg instanceof OpponentTurn)
            {
                this.myTurn = false;
                OpponentTurn opponentTurn = (OpponentTurn) msg;
                ObjectNode event = Json.newObject();
                event.put("type", "changeTurn");
                event.put("player", opponentTurn.name);
                this.out.write(event);
            }
            else if (msg instanceof TellYourCards)
            {
                this.getSender().tell(this.hand, this.getSelf());
            }
            else if (msg instanceof RevealCards)
            {
                this.myTurn = false;
                RevealCards reveal = (RevealCards) msg;
                ObjectNode event = Json.newObject();
                ArrayNode jsCards = event.putArray("cards");
                event.put("type", "resetHand");
                event.put("player", reveal.name);
                event.put("show", true);
                event.put("reveal", true);
                for (Card c : reveal.cards)
                {
                    ObjectNode card = Json.newObject();
                    card.put("rank", c.rank.toString());
                    card.put("suit", c.suit.toString());
                    card.put("id", c.id);
                    jsCards.add(card);
                }
                this.out.write(event);
            }
            else if (msg instanceof NewResults)
            {
                this.myTurn = false;
                NewResults newResults = (NewResults) msg;
                ObjectNode event = Json.newObject();
                event.put("type", "newResults");
                event.put("player", newResults.name);
                event.put("points", newResults.points);
                event.put("wins", newResults.wins);
                this.out.write(event);
            }
            else if (msg instanceof EvaluationsNames)
            {
                EvaluationsNames evalNames = (EvaluationsNames) msg;
                ObjectNode event = Json.newObject();
                event.put("type", "showEvaluations");
                ArrayNode jsEvals = event.putArray("evaluations");
                for (Map.Entry<String, String> entry : evalNames.evaluations
                        .entrySet())
                {
                    ObjectNode eval = Json.newObject();
                    eval.put("player", entry.getKey());
                    eval.put("evaluationName", entry.getValue());
                    jsEvals.add(eval);
                }
                this.out.write(event);
            }
            else if (msg instanceof LicitateNow)
            {
                ObjectNode event = Json.newObject();
                this.myTurn = true;
                event.put("type", "licitate");
                event.put("player", this.name);
                this.out.write(event);
            }
            else
            {
                Logger.error("[    Human    ] Unhandled message (" + this.uuid
                        + "): " + msg.getClass().getName() + ".");
                this.unhandled(msg);
            }
        }
        catch (Exception e)
        {
            this.getSender().tell(new akka.actor.Status.Failure(e),
                    this.getSelf());
            throw e;
        }
    }
}
